package org.scratchcrew.web.cometd.handlers;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.cometd.Client;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.ajax.JSON;
import org.scratchcrew.domain.BoardData;
import org.scratchcrew.domain.Scratch;
import org.scratchcrew.services.BoardService;
import org.scratchcrew.services.ScratchService;
import org.scratchcrew.web.cometd.BayeuxMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component(value = "boardHandler")
public class BoardHandler implements BayeuxMessageHandler {
	private static final String BOARD_SERVICE_CHANNEL_ID = "/scratchcrew/board/**";

	protected final Logger logger = LoggerFactory.getLogger(getClass());
	private static final String CREATE_SCRATCH = "createScratch";
	private static final String POINTING = "pointing";
	private static final String TRANSFORM_SCRATCH = "transformScratch";
	private static final String TRANSFORM_TEXT = "transformText";
	private static final String REMOVE_SCRATCH = "removeScratch";
	private static final Object JOIN = "join";

	@Autowired
	private BoardService boardService;

	@Autowired
	private ScratchService scratchService;

	@Autowired
	private BoardChannelSupport boardChannelSupport;

	@Override
	public String getChannelId() {
		return BOARD_SERVICE_CHANNEL_ID;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object handle(Client fromClient, String channel, Object data,
			String id) {
		if (data instanceof Map && boardChannelSupport.isBoardChannel(channel)) {
			final Map<String, Object> dataMap = (Map<String, Object>) data;
			final long boardId = boardChannelSupport
					.getBoardIdByChannel(channel);
			Object result = handleBoardChannelMessage(boardId, dataMap);
			if (fromClient != null) {
				return result;
			} else {
				return null;
			}
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	private Object handleBoardChannelMessage(Long boardId,
			Map<String, Object> dataMap) {
		Object type = dataMap.get("type");
		if (JOIN.equals(type)) {
			if ("join".equals(dataMap.get("joinType"))) {
				Map<String, Object> initDataMap = createInitDataMap(boardId);
				return initDataMap;
			}
			return createMessage(20000, "No reply on re-join.");
		} else if (POINTING.equals(type)) {
			/*
			 * No return message
			 */
			return null;
		} else if (CREATE_SCRATCH.equals(type)) {
			Object content = dataMap.get("content");
			if (content instanceof Map) {
				Map<String, Object> contentMap = (Map<String, Object>) content;
				Map<String, Object> staticDataMap = (Map<String, Object>) contentMap
						.get("staticData");
				Map<String, Object> scratchDataMap = (Map<String, Object>) contentMap
						.get("scratchData");
				String staticData = JSON.toString(staticDataMap);
				if (staticData == null) {
					logger.warn("static data is not presented" +
							" in createScratch message");
				}
				String scratchData = JSON.toString(scratchDataMap);

				String scratchId = scratchService.createScratch(boardId,
						staticData, scratchData);
				/*
				 * put id for the new shape in the content map
				 */
				contentMap.put("id", scratchId);
			}
			/*
			 * No return message
			 */
			return null;
		} else if (TRANSFORM_SCRATCH.equals(type)) {
			Long scratchId = Long.valueOf(dataMap.get("id").toString());
			String scratchData = scratchService.findScratchData(scratchId);
			Map scratchDataMap = (Map) JSON.parse(scratchData);
			Object transform = dataMap.get("transform");
			logger.debug(transform.toString());
			scratchDataMap.put("matrix", transform);
			scratchService.updateScratchData(JSON.toString(scratchDataMap),
					scratchId);
			/*
			 * No return message
			 */
			return null;
		} else if (TRANSFORM_TEXT.equals(type)) {
			// TODO: what is this?
			return createMessage(1000, "needs improvements");
		} else if (REMOVE_SCRATCH.equals(type)) {
			// TODO: what is this?
			return createMessage(1000, "needs improvements");
		} else {
			// No type or unknown one. We do not know what to do.
			return createMessage(-1, QuotedStringTokenizer
					.quote(type == null ? "null" : type.toString()));
		}
	}

	private Map<String, Object> createInitDataMap(Long boardId) {
		Map<String, Object> uiDataMap = new HashMap<String, Object>();
		BoardData boardData = boardService.findBoardData(boardId);
		Object boardDataMap = JSON.parse(boardData.getBoardData());
		uiDataMap.put("boardData", boardDataMap);
		Object scratchesObject = createScratches(getNotDeleted(boardData
				.getScratches()));
		uiDataMap.put("scratches", scratchesObject);

		Map<String, Object> initDataMap = new HashMap<String, Object>();
		initDataMap.put("type", "initBoard");
		initDataMap.put("id", boardId);
		initDataMap.put("uiData", uiDataMap);
		return initDataMap;
	}

	/*
	 * FIXME:Refactor the code so this wont be needed. For example create query
	 * which make this on DB level, not in server memory!
	 */
	private Set<Scratch> getNotDeleted(Set<Scratch> scratches) {
		Set<Scratch> result = new HashSet<Scratch>();
		for (Scratch scratch : scratches) {
			if (!scratch.getScratchData().isDeleted()) {
				result.add(scratch);
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	private Object[] createScratches(Set<Scratch> scratches) {
		int size = scratches.size();
		Object[] result = new Object[size];
		Iterator<Scratch> scratchesIterator = scratches.iterator();
		for (int i = 0; i < result.length; i++) {
			final Scratch scratch = scratchesIterator.next();
			Object json = JSON.parse("{}");
			result[i] = json;

			logger.trace(String.format("parsed scratch contains %s", json));
			if (json instanceof Map) {
				Map<String, Object> jsonMap = (Map<String, Object>) json;
				Object staticData = JSON.parse(scratch.getStaticData());
				Object scratchData = JSON.parse(scratch.getScratchData()
						.getScratchData());
				jsonMap.put("id", scratch.getId());
				jsonMap.put("staticData", staticData);
				jsonMap.put("scratchData", scratchData);
			} else if (json != null) {
				logger.warn(String.format(
						"json is of class %s instead of java.util.Map", json
								.getClass()));
			}
		}
		return result;
	}

	protected Object createMessage(Integer resultCode, Object message) {
		Map<String, Object> messageMap = new HashMap<String, Object>();
		messageMap.put("resultCode", resultCode);
		messageMap.put("message", message);
		return messageMap;
	}
}
